#include "FKStarter.h"

#include "../FKApplication/FKClientObjectManager.h"
#include "FKObject.h"
#include "../FKApplication/FKFileManager.h"
#include "FKClient.h"
#include "FKEvent.h"
#include "FKMessage.h"
#include "FKClientData.h"
#include "FKPostOperations.h"
#include "FKOSType.h"

#include <QTimer>
#include <QTextStream>
#include <QDir>
#include <iostream>

//#include <QDebug>

FKStarter::FKStarter(int argc, char **argv):QCoreApplication(argc,argv),_inprogress(false),_loaded(0){
    _manager=new FKClientObjectManager();
    _client=0;
    FKSystemObject::addClass<FKEvent>();
    FKSystemObject::addClass<FKClient>();
    FKSystemObject::addClass<FKClientData>();
    FKSystemObject::addClass<FKMessage>();
    _in=new QTextStream(stdin);
    std::cout << std::endl
              << "Hello! Welcome to download client programm." << std::endl
              << "To begin downloading you need join server and log in." << std::endl;
    joinServer();
    QTimer* t=new QTimer(this);
    connect(t,SIGNAL(timeout()),this,SLOT(getCommand()));
    t->start(1000);
}

FKStarter::~FKStarter(){
    delete _manager;
}

void FKStarter::joinServer(){
    forever{
        std::cout << "Enter connection port." << std::endl;
        std::string line1, line2;
        std::getline(std::cin,line1);
        std::cout << "Enter server address." << std::endl;
        std::getline(std::cin,line2);
        std::cout << "Connecting..." << std::endl;
        QString ip=QString::fromStdString(line2);
        int port=QString::fromStdString(line1).toInt();
//        qDebug()<<ip<<port;
        if(_manager->connectToServer(ip,port)){
            connect(_manager,SIGNAL(clientSetted(FKClient*)),this,SLOT(setClient(FKClient*)));
            connect(_manager,SIGNAL(message(QString)),SLOT(message(QString)));
            connect(_manager,SIGNAL(fileRecieved(QString)),SLOT(fileRecieved(QString)));
            connect(_manager,SIGNAL(fileError(QString)),SLOT(fileRecievingError(QString)));
            connect(_manager,SIGNAL(bytesRecieved(QString,qint64)),SLOT(bytesRecieved(QString,qint64)));
            connect(_manager,SIGNAL(disconnected()),SLOT(connectionLost()));
            connect(this,SIGNAL(requestClient(QString,QString)),_manager,SLOT(clientRequested(QString,QString)));
            connect(this,SIGNAL(requestClientCreation(QString,QString)),_manager,SLOT(genClient(QString,QString)));
            connect(this,SIGNAL(requestClientCreation(QString,QString,QVariant)),_manager,SLOT(genClient(QString,QString,QVariant)));
            FKObject::setManager(_manager);
            FKFileManager::setRootPath(applicationDirPath()+QString("/../downloads/bin"));
            break;
        }else{
            std::cout << "Unable connect server. Retry." << std::endl;
        }
    }
    std::cout << "Connection success." << std::endl;
    printHelp();
    std::cout << "Ready to work." << std::endl;
//    QTimer::singleShot(500,this,SLOT(getCommand()));
//    QTimer* t=new QTimer(this);
//    connect(t,SIGNAL(timeout()),this,SLOT(refresh()));
//    t->start(1000);
}

void FKStarter::getCommand(){
    if(_inprogress)return;
        QString answer=_in->readLine();
        if(!answer.compare(QString("e"),Qt::CaseInsensitive)){
            logIn();
        }else if(!answer.compare(QString("n"),Qt::CaseInsensitive)){
            createClient();
        }else if(!answer.compare(QString("r"),Qt::CaseInsensitive)){
            restartApplication();
        }else if(!answer.compare(QString("h"),Qt::CaseInsensitive)){
            printHelp();
        }else if(!answer.compare(QString("u"),Qt::CaseInsensitive)){
            update();
        }else if(!answer.compare(QString("q"),Qt::CaseInsensitive)){
            disconnect(_manager,SIGNAL(disconnected()),this,SLOT(connectionLost()));
            quit();
        }else{
            std::cout << "Uncknown command. Type \"h\" for help." << std::endl;
        }
}

void FKStarter::printHelp(){
    std::cout   << "Use following orders to request actions:" << std::endl
                << "h - print this manual;" << std::endl
                << "n - create new client account;" << std::endl
                << "e - log in existing client account;" << std::endl
                << "u - update application;" << std::endl
                << "r - restart this application;" << std::endl
                << "q - quit this application." << std::endl;
}

void FKStarter::logIn(){
    if(_client){
        std::cout << "Already logged in." << std::endl
                  << "Use \"u\" order to update application." << std::endl;
        return;
    }
    std::string line1, line2;
    std::cout << "Enter client name." << std::endl;
    std::getline(std::cin,line1);
    std::cout << "Enter password." << std::endl;
    std::getline(std::cin,line2);
    QString clientName=QString::fromStdString(line1);
    QString password=QString::fromStdString(line2);
    emit requestClient(clientName,password);
}

void FKStarter::createClient(){
    if(_client){
        std::cout << "Already logged in." << std::endl
                  << "Use \"u\" order to update application." << std::endl;
        return;
    }
    std::string line1, line2;
    std::cout << "Enter client name." << std::endl;
    std::getline(std::cin,line1);
    std::cout << "Enter client password." << std::endl;
    std::getline(std::cin,line2);
    QString clientName=QString::fromStdString(line1);
    QString password=QString::fromStdString(line2);
    emit requestClientCreation(clientName,password);
}

void FKStarter::setClient(FKClient *c){
    _client=c;
    std::cout << "Logged in client account: " << c->name().toStdString() << std::endl
              << "Use \"u\" order to update application." << std::endl;
}

void FKStarter::update(){
    if(!_client){
        std::cout << "Not logged in yet." << std::endl
                  << "Use \"e\" order before continue." << std::endl;
        return;
    }
    qint8 os=FKOSType::curOS;
    qint64 total=0;
    ff=FKFilesList(_client->serverFiles().value<FKFilesList>().filter(os));
    QTextStream out(stdout,QIODevice::WriteOnly);
    out<<QString("Server provides following files:\n");
    out.flush();
    for(int i=0;i<ff.size();++i){
        qint64 w=ff.weight(i);
        total+=w;
        out<<ff.name(i).leftJustified(20)<<ff.version(i)<<"\t\t"<<modifyWeight(w)<<"\n";
    }
    out<<"Total: "<<modifyWeight(total)<<" in "<<ff.size()<<" files.\n"
       <<"Update your application (y/n)?\n";
    out.flush();

    std::string line;
    std::getline(std::cin,line);
    QString answer=QString::fromStdString(line);
    if(!answer.compare(QString("y"),Qt::CaseInsensitive)){
        _inprogress=true;
        _loaded=0;
        _lastP=0;
        _sizes.clear();
        _totalToLoad=total;
        _downloading=ff.names();
        foreach(QString s, _downloading){
            _manager->fileRequest(QString::number(os)+s);
        }
    }else{
        std::cout << "Update rejected." << std::endl;
    }
}

void FKStarter::addPostExecutionCommand(QString command){
    std::cout<<'@'<<QString::number(command.size()).rightJustified(4,'0').toStdString()<<command.toStdString();
}

void FKStarter::connectionLost(){
    std::cout << std::endl << "Connection lost." << std::endl;
    restartApplication();
}

void FKStarter::restartApplication(){
    addPostExecutionCommand(FKPostOperations::restartApplication);
    std::cout << std::endl << "Press \"enter\" to restart the application." << std::endl;
    std::string line;
    std::getline(std::cin,line);
    quit();
}

void FKStarter::message(QString m){
    std::cout << "Attention: " << m.toStdString() << std::endl;
}

QString FKStarter::modifyWeight(qint64 bytes){
    QString swl(" bytes");
    if(bytes>1024){
        bytes/=1024;
        swl=QString(" Kb");
        if(bytes>1000000){
            bytes/=1024*1024;
            swl=QString(" Gb");
        }else if(bytes>1000){
            bytes/=1024;
            swl=QString(" Mb");
        }
    }
    return QString::number(bytes)+swl;
}

void FKStarter::fileRecieved(QString fl){
    std::cout << "\rComplete loading " << fl.remove(0,1).toStdString() << std::endl;
    _downloading.removeAll(fl);
    checkForEnd();
}

void FKStarter::fileRecievingError(QString fl){
    std::cout << "\rError download file " << fl.remove(0,1).toStdString() << std::endl;
    _downloading.removeAll(fl);
    checkForEnd();
}

void FKStarter::checkForEnd(){
    if(_downloading.isEmpty()){
        _inprogress=false;
        std::cout << "Downloading complete." << std::endl;
        std::string line;
        std::cout << "Apply update and restart application (y/n)?" << std::endl;
        std::getline(std::cin,line);
        QString answer=QString::fromStdString(line);
        if(!answer.compare(QString("y"),Qt::CaseInsensitive)){
            std::cout << "Requesting files replace:" << std::endl;
            QDir dir(applicationDirPath()+QString("/../downloads/bin"));
            QMap<QString,QString> m(ff.fileMapAbsolute(dir));
            QString key;
            _postCommands.clear();
            _postCommands.append(FKPostOperations::backUpAndReplaceFiles);
            for(int i=0;i<ff.size();++i){
                key=QString::number(FKOSType::curOS)+ff.name(i);
                if(m.contains(key)){
                    _postCommands.append(m.value(key));
                    _postCommands.append(ff.path(i));
                    addPostExecutionCommand(_postCommands.join(';'));
                    _postCommands.clear();
                    _postCommands.append(FKPostOperations::backUpAndReplaceFiles);
                }
            }
//            addPostExecutionCommand(_postCommands.join(';'));
            restartApplication();
        }else{
            std::cout << "Update rejected." << std::endl;
        }
    }
}

void FKStarter::bytesRecieved(QString fl, qint64 size){
    _sizes[fl]=size;
    _loaded=0;
    foreach(qint64 s,_sizes.values())_loaded+=s;
    qint32 p=_loaded*100/_totalToLoad;
    if(p==_lastP)return;
    _lastP=p;
    std::cout <<"\r"<<p<<"% complete..."<<std::flush;
}
